package Providers
{	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	import mx.core.IChildList;
	import mx.core.UIComponent;
	import mx.managers.ISystemManager;
	
	/**
	 * I wrote this class because I didn't like the behavior
	 * of the built in DragManager.  That said, I wrote it while
	 * looking at DragManagerImpl and DragProxy, so they're similar
	 */
	public class DragProvider
	{
		private static var sysMan:ISystemManager;
		private static var topLevelContainer:DisplayObjectContainer;
		
		//offset is the distance from the victim's origin to the mouse
		//	at the beginning of the drag.
		private static var xOffset:int;
		private static var yOffset:int;
		
		private static var victim:UIComponent;
		private static var currentTarget:IChildList;
		
		private static var priorX:int;
		private static var priorY:int;
		private static var priorTarget:UIComponent;
		
		protected static var isDragging:Boolean = false;
		
		public static function doDrag(Victim:UIComponent, cacheVictim:Boolean=true):void
		{
			if(isDragging)
				return;
			
			isDragging = true;
			sysMan = Victim.systemManager.topLevelSystemManager;
			victim = Victim;
			currentTarget = null;
			
			if(cacheVictim)
				victim.cacheAsBitmap = true;
			
			xOffset = victim.mouseX;
			yOffset = victim.mouseY;
			
			priorX = victim.x;
			priorY = victim.y;
			priorTarget = victim.parent as UIComponent;			
			
			victim.parent.removeChild(victim);
			sysMan.popUpChildren.addChild(victim);
			topLevelContainer = DisplayObjectContainer(sysMan);
			topLevelContainer.addEventListener(MouseEvent.MOUSE_MOVE, topMouseMoveHandler);
			topLevelContainer.addEventListener(MouseEvent.MOUSE_UP, topMouseUpHandler);
			
			//force the initial update of currentTarget
			topMouseMoveHandler(null);
		}
		
		protected static function topMouseMoveHandler(evt:MouseEvent):void
		{
			victim.x = topLevelContainer.mouseX - xOffset;
			victim.y = topLevelContainer.mouseY - yOffset;
			
			var potentialTarget:DisplayObject = findTopReciever();
			
			if(potentialTarget)
				currentTarget = potentialTarget as IChildList;
		}
		
		/**
		 * TopReciever is the DisplayObject highest under our cursor
		 * who will accept our drag, excluding the victim.
		 */
		private static function findTopReciever():DisplayObject
		{
			var point:Point = new Point(topLevelContainer.mouseX, topLevelContainer.mouseY);
			var spritesAtPoint:Array = topLevelContainer.getObjectsUnderPoint(point);
			
			for(var i:int = spritesAtPoint.length-1; i >=0; i--)
			{
				var potentialTarget:DisplayObject = spritesAtPoint[i];
				while(potentialTarget != null)
				{
					if(potentialTarget == victim || victim.contains(potentialTarget))
						break;
						
					var dragEvt:SimpleDragEvent = new SimpleDragEvent(SimpleDragEvent.DRAG_ENTER);
					dragEvt.Victim = victim;
					potentialTarget.dispatchEvent(dragEvt);
					if(dragEvt.Accept)
						return potentialTarget;
					potentialTarget = potentialTarget.parent;
				}
			}
			return null;
		}
		
		protected static function topMouseUpHandler(evt:MouseEvent):void
		{
			if(!currentTarget && !priorTarget)
			{
				//we couldn't add the victim anywhere,
				//	so leave it in the popup layer
				currentTarget = sysMan.popUpChildren;
				throw new Error("couldn't find popup target");
			}
			else if(!currentTarget && priorTarget)
			{
				//the user didn't find an acceptable target,
				//	so stick it back where it was before the drag
				victim.x = priorX;
				victim.y = priorY;
				currentTarget = priorTarget;
			}
			else
			{
				victim.x = UIComponent(currentTarget).contentMouseX - xOffset;
				victim.y = UIComponent(currentTarget).contentMouseY - yOffset;
			}
			
			sysMan.popUpChildren.removeChild(victim);
			currentTarget.addChild(victim);
			
			if(currentTarget is IEventDispatcher)
			{
				var dragEvt:SimpleDragEvent = new SimpleDragEvent(SimpleDragEvent.DRAG_DROP);
				dragEvt.Victim = victim;
				IEventDispatcher(currentTarget).dispatchEvent(dragEvt);
			}
			
			victim.invalidateSize();
			victim.invalidateDisplayList();
			
			victim.cacheAsBitmap = false;
			topLevelContainer.removeEventListener(MouseEvent.MOUSE_MOVE, topMouseMoveHandler);
			topLevelContainer.removeEventListener(MouseEvent.MOUSE_UP, topMouseUpHandler);	
			isDragging = false;		
		}
	}
}